package com.credithc.signature.job.handler;

import com.alibaba.fastjson.JSONObject;
import com.credithc.signature.common.constant.ManagerConstant;
import com.credithc.signature.common.mapper.CallbackLogMapper;
import com.credithc.signature.common.mapper.SignatureLogMapper;
import com.credithc.signature.common.po.CallbackLog;
import com.credithc.signature.common.po.SignatureLog;
import com.credithc.signature.common.ro.callback.CallbackReq;
import com.credithc.signature.common.utils.DateUtil;
import com.credithc.signature.common.utils.TimeUtil;
import com.credithc.signature.job.constant.CallbackConstant;
import com.credithc.signature.job.constant.DelayTypeEnum;
import com.credithc.signature.job.mq.CallbackSender;
import com.credithc.signature.job.ro.CallRequest;
import com.credithc.signature.job.ro.HandleRequest;
import com.credithc.signature.job.ro.JobRequest;
import com.credithc.signature.job.utils.HttpClientProxy;
import com.credithc.signature.job.utils.SpringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import tk.mybatis.mapper.entity.Example;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * @author zhanghanqiu181212@credithc.com
 * @Date 2021/6/29 15:18
 */
@Slf4j
public class CallbackHandler extends AbstractExecuteHandler {

    private String handlerName = "回调业务";

    @Autowired
    private static CallbackSender retryProvider;

    @Autowired
    private static SignatureLogMapper signatureLogMapper;

    @Autowired
    private static CallbackLogMapper callbackLogMapper;


    static {
        retryProvider = SpringUtil.getBean(CallbackSender.class);
        signatureLogMapper = SpringUtil.getBean(SignatureLogMapper.class);
        callbackLogMapper = SpringUtil.getBean(CallbackLogMapper.class);
    }

    public CallbackHandler() {
        this.handlerName = handlerName;
    }


    @Override
    public void handleRequest(HandleRequest handleRequest) throws Exception {
        if (Objects.nonNull(handleRequest)) {
            JobRequest jobRequest = handleRequest.getJobRequest();
            String requestSn = jobRequest.getRequestSn();

            try {
                // 1、执行回调流程
                CallRequest callRequest = handleRequest.getCallRequest();
                CallbackReq callbackReq = new CallbackReq();
                BeanUtils.copyProperties(callRequest, callbackReq);
                callBackMore(callbackReq);
                log.info("－消息－:requestSn={},【{}】已经处理;流程结束", requestSn, handlerName);

                // 2、判断是否有下一个
                hasNextHandler(requestSn, handleRequest);
            } catch (Exception e) {
                e.printStackTrace();
                log.error("－消息－:requestSn={},执行【{}】流程时发生异常;总流程结束\n{}", requestSn, handlerName, e.getCause());
            }
        }
    }

    /**
     * 判断是否还有下一个流程:如果有，则继续执行
     */
    private void hasNextHandler(String requestSn, HandleRequest handleRequest) throws Exception {
        if (null != this.nextHandler) {
            this.nextHandler.handleRequest(handleRequest);
        } else {
            log.info("－消息－:requestSn={},【{}】无下一个执行流程;总流程结束", requestSn, handlerName);
            return;
        }
    }

    /**
     * 回调多次
     *
     * @param callbackReq
     */
    private void callBackMore(CallbackReq callbackReq) {
        CallbackLog callbackLog = null;
        try {
            //1.如果是第一次回调则插入一条记录，否则只更新
            String requestSN = callbackReq.getRequestSN();
            callbackLog = callbackLogMapper.selectByRequestNo(requestSN);
            if (callbackLog == null) {
                callbackLog = insertDB(callbackReq);
            }
            //2.发起回调
            callback(callbackReq, callbackLog);
        } catch (Exception e) {
            log.error("－消息－: requestSn={},回调业务系统失败,错误信息如下\n", callbackReq.getRequestSN(), e.getCause());
            callbackLog.setRecallMsg(e.getMessage());
            //发起重试机制
            retryMessageOnDelay(callbackReq, callbackLog);
        }
    }

    /**
     * 回调业务系统
     *
     * @param callbackReq
     * @param callbackLog
     */
    private void callback(CallbackReq callbackReq, CallbackLog callbackLog) throws Exception {
        String result = null;
        try {
            result = HttpClientProxy.post(callbackReq.getCallBackURL(), buildRes(callbackReq));
            callbackLog.setRecallStatus(CallbackConstant.SUCCESS);
            callbackLog.setRecallMsg(result);
            updateDB(callbackLog);
            updateReqLogDB(callbackLog, true);
            log.debug("回调业务系统成功...... requestSN=" + callbackReq.getRequestSN());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 使用自定义延时队列
     *
     * @param callbackReq
     * @param callbackLog
     */
    private void retryMessageOnDelay(CallbackReq callbackReq, CallbackLog callbackLog) {
        // 1、更新数据库
        callbackLog.setRecallStatus(CallbackConstant.FAIL);
        if (callbackLog.getRetryTime() > 5) {
            callbackLog.setHasNext(false);
        }
        callbackLog.setHasNext(true);
        callbackLog.setRetryTime(callbackLog.getRetryTime() + 1);
        DelayTypeEnum delayTypeEnum = DelayTypeEnum.getDelayTypeEnumByType(callbackLog.getRetryTime());
        int delayValue = delayTypeEnum.getDelayValue();
        callbackLog.setNextTime(TimeUtil.getExpireTime2(delayValue));
        callbackReq.setRetryTime(callbackLog.getRetryTime());
        //更新回调信息表
        updateDB(callbackLog);
        //更新请求流水表
        updateReqLogDB(callbackLog, false);
        // 2、放入延时队列
        retryProvider.sendDelayMsg(callbackReq);
    }

    /**
     * 插入数据库
     *
     * @param callbackReq
     */
    private CallbackLog insertDB(CallbackReq callbackReq) {
        CallbackLog callbackLog = new CallbackLog();
        callbackLog.setHasNext(false);
        callbackLog.setRetryTime(0);
        callbackLog.setRequestNo(callbackReq.getRequestSN());
        callbackLog.setSignStatus(callbackReq.getIsSuccess() ? Integer.valueOf(ManagerConstant.SUCCESS) : Integer.valueOf(ManagerConstant.FAIL));
        callbackLog.setRecallStatus(CallbackConstant.NONE);
        callbackLog.setCallbackUrl(callbackReq.getCallBackURL());
        callbackLog.setContractUri(callbackReq.getContractUri());
        callbackLog.setCreateTime(new Date());
        callbackLog.setUpdateTime(new Date());
        try {
            callbackLogMapper.insertData(callbackLog);
            log.debug("数据插入成功......callbackReq={}", callbackReq);
        } catch (Exception e) {
            log.error("数据插入失败......callbackReq={}", e);
        }
        return callbackLog;
    }

    /**
     * 更新数据库状态
     *
     * @param callbackLog
     */
    private void updateDB(CallbackLog callbackLog) {
        //更新回调表信息
        callbackLog.setUpdateTime(new Date());
        callbackLogMapper.updateByPrimaryKeySelective(callbackLog);
    }

    /**
     * 更新请求表信息
     *
     * @param callbackLog
     */
    private void updateReqLogDB(CallbackLog callbackLog, boolean success) {
        //更新请求流水表
        SignatureLog signatureLog = new SignatureLog();
        if (success) {
            signatureLog.setRequestSn(callbackLog.getRequestNo());
            signatureLog.setSuccessCallCode(ManagerConstant.CALL_SUCCESS);
            signatureLog.setSuccessCallMsg(callbackLog.getRecallMsg());
            signatureLog.setFailCallNum(0);
        } else {
            signatureLog.setRequestSn(callbackLog.getRequestNo());
            signatureLog.setSuccessCallCode(ManagerConstant.CALL_FAIL);
            signatureLog.setSuccessCallMsg(callbackLog.getRecallMsg());
            signatureLog.setFailCallNum(callbackLog.getRetryTime());
        }
        Example e = new Example(SignatureLog.class);
        Example.Criteria criteria = e.createCriteria();
        criteria.andEqualTo("requestSn", signatureLog.getRequestSn());
        criteria.andLessThanOrEqualTo("createTime", DateUtil.getNextDate(1));
        criteria.andGreaterThanOrEqualTo("createTime", DateUtil.getBeforeDate(2));
        signatureLogMapper.updateByExampleSelective(signatureLog, e);
    }

    /**
     * 封装回调信息
     *
     * @param callbackReq
     * @return
     */
    private Map<String, String> buildRes(CallbackReq callbackReq) {
        Map<String, String> params = new HashMap<>(16);
        JSONObject jsonObj = new JSONObject();
        jsonObj.put("retCode", callbackReq.getIsSuccess() ? 0 : -1);
        jsonObj.put("retMsg", callbackReq.getErrorMsg());
        jsonObj.put("fileUri", callbackReq.getContractUri());
        params.put("jsonData", jsonObj.toJSONString());
        log.debug("回调业务系统消息：" + params.toString());
        return params;
    }
}
